/**
 * 
 */
package tools;

import javax.swing.text.Utilities;

import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.DecompositionSolver;
import org.apache.commons.math3.linear.IterativeLinearSolver;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import interfaces.ILinearEquation;

/**
 *  This class uses and implements Linear equations creator and solver.
 * 
 * @author Vivek
 *
 */
public class LinearEquationToolkit implements ILinearEquation  {

	private double[][] coef;   // contains that coeficients 
	private int[] degree;
	private double[][] xy;
	public RealVector constants;
	public RealMatrix coefficients;
	public RealVector solution;

	
	public LinearEquationToolkit() {
		super();

	}

	public void createEquations(double[][] xy) {
		
		this.xy = xy;
//		System.out.println(UtilFunctions.display2DArray(xy));
		coef = new double[xy[0].length-1][this.computeDegree()];
		degree = new int[xy[0].length-1];
		coefficients = new Array2DRowRealMatrix(this.computeDegree(),this.computeDegree());
		constants = new ArrayRealVector(this.computeDegree());

		createBasicEquations();
	
	}
	
	/**
	 * Returns that degree of polynomial required.
	 * 
	 */
	private int computeDegree() {
		return (this.xy[0].length-1)*this.xy.length; // first value is x, hence excluded
	}
	
	/**
	 *  Creates the series of polynomial equations that can be later used to 
	 *  create linear equations
	 */
	private void createBasicEquations() {
		
		for(int l = 0;l < coef[0].length;l++ ){  // Set the coefficient for zeroth differential equation.
			coef[0][l] = 1;
		}
		
		degree[0] = this.computeDegree();  // Set the degree for zeroth differential equation.
		
//		System.out.println(coef.length+" "+degree[0]);
		
			for (int n = 1;n < coef.length;n++){     	// Loop through all the differential states
				degree[n] = degree[n-1];
//				System.out.println("n="+n+" degree[n]="+degree[n]);
				for(int l = 1;l < degree[n];l++ ){     	// Loop and compute all the coefficients for current differential state 
					coef[n][l-1]= coef[n-1][l]*l;
				}
				degree[n] = degree[n] - 1;
			}
	}

	
	/* 
	 * (non-Javadoc)
	 * @see interfaces.ILinearEquation#displayEquation()
	 */
	public String getEquation() {
		int n =0;
		String s = "";
		int l;
		for(l = 0;l < solution.getDimension()-1;l++ ){
			s += solution.getEntry(l)+",";
		}
		s += solution.getEntry(l);

		return s;
	}

	@Override
	public void solveEquation() {
		
		for(int i=0;i < xy.length;i++ ){
			for (int n = 0;n < coef.length;n++){
				for(int l = 0;l < coef[n].length;l++ ){
//					s1 += (coef[n][l]*Math.pow(xy[i][0],l))+ "  ";
					coefficients.setEntry(coef.length*i+n,l,coef[n][l]*Math.pow(xy[i][0],l));
				}
				
				constants.setEntry(i*coef.length+n,xy[i][n+1]);		
								
			}		
//			System.out.print("Constants:"+constants.toString());
//			System.out.print("Index: "+constants.getMaxIndex());
////			System.out.println("S1 "+s1);
//			System.out.println("coeffi :"+coefficients.toString());

		}
		
//		System.out.println(coefficients.toString());
//		System.out.println(constants.toString());		
		
		
		DecompositionSolver solver = new LUDecomposition(coefficients,0.000000000000000001).getSolver();
		

		
		solution = solver.solve(constants);	

//		System.out.println("this is the solution");
//		System.out.println(constants.toString());				
//		System.out.println(coefficients.toString());
//		System.out.println(solution.toString());
		
	}
	
	/**
	 * 
	 * This function gets that points between two points. 
	 * Private function will be used in to get the predicted points after the polynomial equation is solved.
	 * 
	 * @param x1
	 * @param x2
	 * @param countOfPredict
	 * @return
	 */
	private double[] getIntermediatePoints(double startPoint,double endPoint,int countOfPredict){
		
		int parts = countOfPredict+1;
		double[] predictedPoints = new double[countOfPredict];
		
		for (int countPoints=1;countPoints < parts;countPoints++){
			predictedPoints[countPoints-1]= startPoint + countPoints*((endPoint - startPoint)/parts);
		}
		return predictedPoints;
	}
	
	
	/**
	 * Given count of predicted values needed this function returns set of points to apply smoothing. 
	 * 
	 * @param countOfPredict
	 * @return
	 */
	public double[][] getPredicted(int countOfPredict){
		
		double[][] coordinates = new double[countOfPredict+2][2];
		double startPoint = this.xy[this.xy.length-2][0];
//		System.out.println("startPoint:"+startPoint);
		double endPoint = this.xy[this.xy.length-1][0];
//		System.out.println("endPoint:"+endPoint);
		
		double[] intermediateVal = getIntermediatePoints(startPoint,endPoint,countOfPredict);
		
		coordinates[0][0] = startPoint;
		coordinates[0][1] = this.xy[this.xy.length-2][1];
		
		int n = 0;
		for (n =0;n < intermediateVal.length;n++){
			coordinates[n+1][0] = intermediateVal[n];			
			for(int l = 0;l < solution.getDimension();l++ ){
				coordinates[n+1][1] = coordinates[n][1] + solution.getEntry(l)*Math.pow(intermediateVal[n], l);
			}
		}
		coordinates[n+1][0] = endPoint;
		coordinates[n+1][1] = this.xy[this.xy.length-1][1];
		
		return coordinates;
	}
	
	/**
	 * This function returns the polynomial equations created.
	 * 
	 * @return
	 */
	public String[] getAllPolynomial(){
		String[] s = new String[coef.length];
		for (int n = 0;n < coef.length;n++){
			s[n] = "{";
			for(int l = 0;l < coef[n].length-1-n;l++ ){
				s[n] += coef[n][l]+",";
			}
			s[n] += coef[n][coef[n].length-1-n]+","+ (coef[n].length-1-n)+"}";
		}
		return s;
	}
	
	/**
	 * This function displays the polynomial equation created
	 */
	public void displayPoly(){
		for (int n = 0;n < coef.length;n++){
			String s = "";
			
			for(int l = 0;l < coef[n].length-1-n;l++ ){
				s += coef[n][l]+"*X^" + l+" + ";
			}
			s += coef[n][coef[n].length-1-n]+"*X^" + (coef[n].length-1-n);
			System.out.println(s);
		
		}		
	}

}
